package steed.util.wechat;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.locks.Lock;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.struts2.ServletActionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

import steed.domain.GlobalParam;
import steed.domain.system.Property;
import steed.domain.wechat.WechatAccount;
import steed.domain.wechat.WechatUser;
import steed.exception.runtime.system.FrameworkException;
import steed.exception.runtime.wechat.AttachTypeNotSupportedException;
import steed.exception.runtime.wechat.WechatIoException;
import steed.hibernatemaster.util.DaoUtil;
import steed.hibernatemaster.util.DaoUtil.ImmediatelyTransactionData;
import steed.hibernatemaster.util.QueryBuilder;
import steed.util.base.BaseUtil;
import steed.util.base.ContextUtil;
import steed.util.base.PathUtil;
import steed.util.base.StringUtil;
import steed.util.digest.Md5Util;
import steed.util.file.FileUtil;
import steed.util.http.HttpUtil;
import steed.util.system.DataCacheUtil;
import steed.util.system.FlowUtil;
import steed.util.system.SynchronizedUtil;
import steed.util.wechat.domain.result.AccessToken;
import steed.util.wechat.domain.result.ArticleSummaryResult;
import steed.util.wechat.domain.result.AttachUploadResult;
import steed.util.wechat.domain.result.BaseWechatResult;
import steed.util.wechat.domain.result.GetTemplateIdResult;
import steed.util.wechat.domain.result.JsapiTicket;
import steed.util.wechat.domain.result.MenuResult;
import steed.util.wechat.domain.result.MessageResult;
import steed.util.wechat.domain.result.Openids;
import steed.util.wechat.domain.result.OrderQueryResult;
import steed.util.wechat.domain.result.QrcodeResult;
import steed.util.wechat.domain.result.RedPacketResult;
import steed.util.wechat.domain.result.RefundResult;
import steed.util.wechat.domain.result.ShopOrderQueyResult;
import steed.util.wechat.domain.result.ShortcutUrlResult;
import steed.util.wechat.domain.result.TemplateMessageResult;
import steed.util.wechat.domain.result.UnifiedOrderResult;
import steed.util.wechat.domain.result.WechatResult;
import steed.util.wechat.domain.result.WechatUsers;
import steed.util.wechat.domain.send.Article;
import steed.util.wechat.domain.send.ArticleSummarySend;
import steed.util.wechat.domain.send.Articles;
import steed.util.wechat.domain.send.GetTemplateIdSend;
import steed.util.wechat.domain.send.Menu;
import steed.util.wechat.domain.send.MessagePriview;
import steed.util.wechat.domain.send.MessageSend;
import steed.util.wechat.domain.send.OrderQuerySend;
import steed.util.wechat.domain.send.RedPacketSend;
import steed.util.wechat.domain.send.RefundSend;
import steed.util.wechat.domain.send.SetIndustrySend;
import steed.util.wechat.domain.send.TemplateMessageSend;
import steed.util.wechat.domain.send.UnifiedOrderSend;
import steed.util.wechat.domain.send.UserQueryDomain;
import steed.util.wechat.domain.sys.PageAccessToken;

/**
 * 微信接口调用工具类
 * @author 战马
 *
 */
@SuppressWarnings("deprecation")
public class WechatInterfaceInvokeUtil {
	private static final Logger log = LoggerFactory.getLogger(WechatInterfaceInvokeUtil.class);
	public static final String wechatUserSessionKey = "wechatUser";
	
	/**
	 * 把AppID，AppSecret,ACCESS_TOKEN装配到url
	 * @param url
	 * @return
	 */
	public static String fitParam2Url(String url){
		String temp = url.replace("#APPID#", MutiAccountSupportUtil.getWechatAccount().getAppID());
		if (temp.contains("#ACCESS_TOKEN#")) {
			AccessToken accessToken = AccessTokenUtil.getAccessToken();
			if (accessToken == null || accessToken.getAccess_token() == null) {
				BaseUtil.getLogger().error("获取accessToken失败!");
				temp = temp.replace("#ACCESS_TOKEN#", "");
			}else {
				temp = temp.replace("#ACCESS_TOKEN#", accessToken.getAccess_token());
			}
		}
		return temp.replace("#APPSECRET#", MutiAccountSupportUtil.getWechatAccount().getAppSecret());
	}
	
	/*******************************\测试***********************/
	
	
	/*******************************#测试***********************/
	/*******************************\接口***********************/
	
	
	/*******************************\微信小店***********************/
	
	/**
	 * 根据订单状态/创建时间获取订单详情
	 * @param status 订单状态(不带该字段-全部状态, 2-待发货, 3-已发货, 5-已完成, 8-维权中, )
	 * @param beginTime 订单创建时间起始时间(不带该字段(传null)则不按照时间做筛选)
	 * @param endTime 订单创建时间终止时间(不带该字段(传null)则不按照时间做筛选)
	 * @return
	 */
	public static ShopOrderQueyResult getOrderByFilter(int status,Long beginTime,Long endTime){
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("status", status);
		map.put("begintime", beginTime);
		map.put("endtime", endTime);
		return invokeWechatInterface(map, ShopOrderQueyResult.class, fitParam2Url(WechatConstantParamter.getOrderByFilteUrl));
	}
	/**
	 * 设置订单发货信息
	 * order_id	订单ID
	delivery_company	物流公司ID(参考《物流公司ID》；
	当need_delivery为0时，可不填本字段；
	当need_delivery为1时，该字段不能为空；
	当need_delivery为1且is_others为1时，本字段填写其它物流公司名称)
	delivery_track_no	运单ID(
	当need_delivery为0时，可不填本字段；
	当need_delivery为1时，该字段不能为空；
	)
	need_delivery	商品是否需要物流(0-不需要，1-需要，无该字段默认为需要物流)
	is_others	是否为6.4.5表之外的其它物流公司(0-否，1-是，无该字段默认为不是其它物流公司)
	 * @param order_id
	 * @param delivery_company
	 * @param delivery_track_no
	 * @param need_delivery
	 * @param is_others
	 * @return
	 */
	public static BaseWechatResult setDelivery(String order_id,String delivery_company,String delivery_track_no,Integer need_delivery,Integer is_others){
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("order_id", order_id);
		map.put("delivery_company", delivery_company);
		map.put("delivery_track_no", delivery_track_no);
		map.put("need_delivery", need_delivery);
		map.put("is_others", is_others);
		return invokeWechatInterface(map, BaseWechatResult.class, fitParam2Url(WechatConstantParamter.setDeliveryUrl));
	}
	
	
	
	/*******************************#微信小店***********************/
	
	
	/**
	 * 上传图文消息接口
	 * @param articles
	 * @return
	 */
	public static AttachUploadResult uploadNews(Articles articles){
		return BaseUtil.parseJson(HttpUtil.getRequestString(HttpUtil.http_post, fitParam2Url(WechatConstantParamter.uploadNewsUrl), null, null, BaseUtil.getJson(articles)), AttachUploadResult.class);
	}
	/**
	 * 上传图文消息接口
	 * @param articles
	 * @return
	 */
	public static AttachUploadResult uploadNews(List<Article> articles){
		Articles articles2 = new Articles();
		articles2.setArticles(articles);
		return uploadNews(articles2);
	}
	
	/*************************\模板消息******************************/
	/**
	 * 设置模板消息所属行业
	 * @param send
	 */
	public static BaseWechatResult setIndustry(SetIndustrySend send){
		return invokeWechatInterface(send, BaseWechatResult.class, fitParam2Url(WechatConstantParamter.setIndustryUrl));
	}
	
	/**
	 * @see #getTemplateId
	 * @param send
	 * @return
	 */
	@Deprecated
	public static GetTemplateIdResult getTemplateId(GetTemplateIdSend send){
		return invokeWechatInterface(send, GetTemplateIdResult.class, fitParam2Url(WechatConstantParamter.getTemplateIdUrl));
	}
	/**
	 * 获取模板ID
	 * @param template_id_short
	 * @return
	 */
	public static GetTemplateIdResult getTemplateId(String template_id_short){
		Property property = new Property();
		property.setPropertyType("tempTemplateID_" + template_id_short);
		String appID = MutiAccountSupportUtil.getWechatAccount().getAppID();
		property.setKee(appID);
		
		Lock lock = SynchronizedUtil.getLock(appID+template_id_short,"steed.util.wechat.WechatInterfaceInvokeUtil.getTemplateId(String)");
		lock.lock();
		
			ImmediatelyTransactionData immediatelyTransactionData = DaoUtil.immediatelyTransactionBegin();
			Property property2 = property.smartGet();
			DaoUtil.immediatelyTransactionEnd(immediatelyTransactionData);
			if (property2 != null) {
				GetTemplateIdResult result = new GetTemplateIdResult();
				result.setTemplate_id(property2.getValue());
				return result;
			}
			
			Map<String, Object> map = new HashMap<String, Object>();
			map.put("template_id_short", template_id_short);
			GetTemplateIdResult invokeWechatInterface = invokeWechatInterface(map, GetTemplateIdResult.class, fitParam2Url(WechatConstantParamter.getTemplateIdUrl));
			if (invokeWechatInterface.isSuccess()) {
				ImmediatelyTransactionData immediatelyTransactionBegin = DaoUtil.immediatelyTransactionBegin();
				property.setValue(invokeWechatInterface.getTemplate_id());
				property.save();
				DaoUtil.managTransaction();
				DaoUtil.immediatelyTransactionEnd(immediatelyTransactionBegin);
			}
			
		lock.unlock();
		return invokeWechatInterface;
		
	}
	
	
	
	/**
	 * 发送模板消息
	 * @param send
	 * @return
	 */
	public static TemplateMessageResult sendTemplateMessage(TemplateMessageSend send){
		return invokeWechatInterface(send, TemplateMessageResult.class, fitParam2Url(WechatConstantParamter.sendTemplateMessageUrl));
	}
	
	/**
	 * 当公众号关注者数量超过10000时，可通过填写next_openid的值，从而多次拉取列表的方式来满足需求。<br>
	 *
	 *	具体而言，就是在调用接口时，将上一次调用得到的返回中的next_openid值，作为下一次调用中的next_openid值。<br>
	 *
	 * @param nextOpenid	第一个拉取的OPENID，不填默认从头开始拉取
	 * @return
	 */
	public static Openids getOpenids(String nextOpenid){
		if (nextOpenid == null) {
			nextOpenid = "";
		}
		return invokeWechatInterface(null, Openids.class, 
				fitParam2Url(WechatConstantParamter.getOpenidsUrl.replace("#NEXT_OPENID#", nextOpenid)));
	}
	
	/**
	 * 发送模板消息
	 * @param send
	 * @return
	 */
	public static TemplateMessageResult sendTemplateMessage(TemplateMessageSend send,String template_id_short){
		String template_id = null;
		if (template_id_short != null && send.getTemplate_id() == null) {
			template_id = getTemplateId(template_id_short).getTemplate_id();
			send.setTemplate_id(template_id);
		}
		TemplateMessageResult invokeWechatInterface = invokeWechatInterface(send, TemplateMessageResult.class, fitParam2Url(WechatConstantParamter.sendTemplateMessageUrl));
		//"模板id不存在错误码"40037
		if (template_id != null && !invokeWechatInterface.isSuccess() && 40037 == invokeWechatInterface.getErrcode()) {
			Lock lock = SynchronizedUtil.getLock(template_id,"deleteInvalidTemplateIdCache");
			lock.lock();
			
				BaseUtil.getLogger().warn("删除失效模板{}",template_id);
				Property property = new Property();
				property.setPropertyType("tempTemplateID_" + template_id_short);
				property.setKee(MutiAccountSupportUtil.getWechatAccount().getAppID());
				ImmediatelyTransactionData immediatelyTransactionBegin = DaoUtil.immediatelyTransactionBegin();
				boolean delete = property.delete();
				DaoUtil.managTransaction();
				DaoUtil.immediatelyTransactionEnd(immediatelyTransactionBegin);
				property = property.smartGet();
				if (property != null) {
					DaoUtil.evict(property);
				}
				if (delete) {
					send.setTemplate_id(null);
					sendTemplateMessage(send, template_id_short);
				}
				
			lock.unlock();
		}
		return invokeWechatInterface;
	}
	
	
	
	/*************************#模板消息******************************/
	/*************************\图文统计******************************/
	/**
	 * 获取图文群发每日数据接口
	 * @return
	 */
	public static ArticleSummaryResult getArticleSummary(ArticleSummarySend articleSummarySend){
		return invokeArticleSummaryInterface(articleSummarySend, WechatConstantParamter.articleSummaryUrl);
	}
	/**
	 * 获取图文群发总数据接口
	 * @return
	 */
	public static ArticleSummaryResult getArticleTotal(ArticleSummarySend messageCount){
		return invokeArticleSummaryInterface(messageCount, WechatConstantParamter.getArticleTotalUrl);
	}
	/**
	 * 获取图文统计数据接口
	 * @return
	 */
	public static ArticleSummaryResult getUserRead(ArticleSummarySend messageCount){
		return invokeArticleSummaryInterface(messageCount, WechatConstantParamter.getUserReadUrl);
	}
	/**
	 * 获取图文统计分时数据接口
	 * @return
	 */
	public static ArticleSummaryResult getUserReadHour(ArticleSummarySend messageCount){
		return invokeArticleSummaryInterface(messageCount, WechatConstantParamter.getUserReadHourUrl);
	}
	/**
	 * 获取图文分享转发数据接口
	 * @return
	 */
	public static ArticleSummaryResult getUserShare(ArticleSummarySend messageCount){
		return invokeArticleSummaryInterface(messageCount, WechatConstantParamter.getUserShareUrl);
	}
	/**
	 * 获取图文分享转发分时数据接口
	 * @return
	 */
	public static ArticleSummaryResult getUserShareHour(ArticleSummarySend messageCount){
		return invokeArticleSummaryInterface(messageCount, WechatConstantParamter.getUserShareHourUrl);
	}
	
	private static ArticleSummaryResult invokeArticleSummaryInterface(ArticleSummarySend messageCount,String url){
		return invokeWechatInterface(messageCount, ArticleSummaryResult.class, url);
	}
	/*************************#图文统计******************************/
	
	/**
	 * 设置菜单接口
	 * @param articles
	 * @return
	 */
	public static BaseWechatResult setMenu(Menu menu){
//		return invokeWechatInterface(menu, BaseWechatResult.class, fitParam2Url(WechatConstantParamter.setMenuUrl));
		return BaseUtil.parseJson(HttpUtil.getRequestString(HttpUtil.http_post, fitParam2Url(WechatConstantParamter.setMenuUrl), null, null, BaseUtil.getJson(menu)), BaseWechatResult.class);
	}
	/**
	 * 获取菜单接口
	 * @param articles
	 * @return
	 */
	public static Menu getMenu(){
		return BaseUtil.parseJson(HttpUtil.getRequestString(HttpUtil.http_get, fitParam2Url(WechatConstantParamter.getMenuUrl), null, null, null),MenuResult.class).getMenu();
	}
	
	/**
	 * 预览消息接口
	 * @param articles
	 * @return
	 */
	public static MessageResult previewMessage(MessagePriview messagePriview){
		String requestString = HttpUtil.getRequestString(HttpUtil.http_post, fitParam2Url(WechatConstantParamter.messagePreviewUrl), null, null, BaseUtil.getJson(messagePriview));
		return BaseUtil.parseJson(requestString, MessageResult.class);
	}
	
	/**
	 * 根据分组发送消息接口
	 * @param articles
	 * @return
	 */
	public static MessageResult sendMessageByGroup(MessageSend messageSend){
		String requestString = HttpUtil.getRequestString(HttpUtil.http_post, fitParam2Url(WechatConstantParamter.sendMessageByGroupPreviewUrl), null, null, BaseUtil.getJson(messageSend));
		return BaseUtil.parseJson(requestString, MessageResult.class);
	}
	/**
	 * 获取临时二维码
	 * expire_seconds 	该二维码有效时间，以秒为单位。 最大不超过2592000（即30天），此字段如果不填(传null)，则默认有效期为30天。
		scene_id 	场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1--100000）
	 * @return
	 */
	public static QrcodeResult getTempQrcode(Long expire_seconds,int scene_id){
		Map<String, Object> map = new HashMap<String, Object>();
		if (expire_seconds == null) {
			expire_seconds = 2592000L;
		} 
		map.put("expire_seconds",expire_seconds);
		Map<String, Object> action_info = new HashMap<String, Object>();
		Map<String, Object> scene = new HashMap<String, Object>();
		scene.put("scene_id", scene_id);
		action_info.put("scene", scene);
		map.put("action_info",action_info);
		map.put("action_name","QR_SCENE");
		String requestString = HttpUtil.getRequestString(HttpUtil.http_post, fitParam2Url(WechatConstantParamter.getQrcodeUrl), null, null, BaseUtil.getJson(map));
		return BaseUtil.parseJson(requestString, QrcodeResult.class);
	}
	
	/**
	 * 获取参数二维码scene_str == null时为临时二维码,否则是获取永久二维码
	action_name 	二维码类型，QR_SCENE为临时,QR_LIMIT_SCENE为永久,QR_LIMIT_STR_SCENE为永久的字符串参数值
	action_info 	二维码详细信息
	scene_id 	场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1--100000）
	scene_str 	场景值ID（字符串形式的ID），字符串类型，长度限制为1到64，仅永久二维码支持此字段 
	 * @return
	 */
	public static QrcodeResult getPermanentQrcode(String scene_str,Integer scene_id){
		Map<String, Object> map = new HashMap<String, Object>();
		Map<String, Object> action_info = new HashMap<String, Object>();
		Map<String, Object> scene = new HashMap<String, Object>();
		scene.put("scene_id", scene_id);
		scene.put("scene_str", scene_str);
		action_info.put("scene", scene);
		map.put("action_info",action_info);
		if (scene_str != null) {
			map.put("action_name","QR_LIMIT_STR_SCENE");
		}else {
			map.put("action_name","QR_SCENE");
			map.put("expire_seconds",2592000);
		}
		String requestString = HttpUtil.getRequestString(HttpUtil.http_post, fitParam2Url(WechatConstantParamter.getQrcodeUrl), null, null, BaseUtil.getJson(map));
		return BaseUtil.parseJson(requestString, QrcodeResult.class);
	}
	/**
	 * 长链接转短连接
	 * @param long_url
	 * @return 短链接
	 */
	public static ShortcutUrlResult shorturl(String long_url){
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("action", "long2short");
		map.put("long_url", long_url);
		return invokeWechatInterface(map, ShortcutUrlResult.class, WechatConstantParamter.getShortUrl);
	}
	
	/**
	 * 从微信服务器获取JsapiTicket,不会缓存JsapiTicket，
	 * 请调用JsapiTicketUtil
	 * @see steed.util.wechat.JsapiTicketUtil
	 */
	public static JsapiTicket getJsapiTicket(){
		String url = WechatInterfaceInvokeUtil.fitParam2Url(WechatConstantParamter.getJsapiTicketUrl);
		String json = HttpUtil.getRequestString(HttpUtil.http_get, url, null, null);
		JsapiTicket data = new Gson().fromJson(json, JsapiTicket.class);
		data.setAccess_token_getTime(System.currentTimeMillis());
		log.debug("JsapiTicket----->"+data.getTicket());
		return data;
	}
	
	/**
	 * 从微信服务器获取accessToken,不会缓存accessToken，
	 * 请调用steed.util.wechat.AccessTokenUtil
	 * @see steed.util.wechat.AccessTokenUtil
	 */
	public static AccessToken getAccessToken(){
		String url = WechatInterfaceInvokeUtil.fitParam2Url(WechatConstantParamter.getAccessTokenUrl);
		String json = HttpUtil.getRequestString(HttpUtil.http_get, url, null, null);
		AccessToken data = new Gson().fromJson(json, AccessToken.class);
		data.setAccess_token_getTime(System.currentTimeMillis());
		log.debug("accessToken----->"+data.getAccess_token());
		return data;
	}
	
	/**
	 * 获取用户基本信息
	 * @param openid
	 */
	public static WechatUser getUsetInfo(String openid){
		String requestString = HttpUtil.getRequestString(HttpUtil.http_get, fitParam2Url("https://api.weixin.qq.com/cgi-bin/user/info?access_token=#ACCESS_TOKEN#&openid=#OPENID#&lang=zh_CN").replace("#OPENID#", openid), null, null, null);
		log.debug("拉取到的用户信息--->{}",requestString);
		return BaseUtil.parseJson(requestString, WechatUser.class);
	}
	
	public static void getUserInformation(HttpServletResponse response, HttpServletRequest request,boolean getAllInformation,boolean login){
		try {
			String scope = "snsapi_base";
			if (getAllInformation) {
				scope = "snsapi_userinfo";
			}
			response.sendRedirect(getUserSNSUrl(getAllInformation, login, PathUtil.getQueryUrl(request),WechatConstantParamter.getAuthorCodeUrl,scope));
		} catch (IOException e) {
			throw new FrameworkException(e);
		}
	}
	
	/**
	 * 微信网页扫码登陆
	 * @param response
	 * @param request
	 * @param getAllInformation 是否获取用户的昵称等其他信息,若为false则只获取openid用
	 * @param login 是否把登陆的微信用户存到session
	 * 
	 */
	private static void qrconnectLogin(HttpServletResponse response, HttpServletRequest request,boolean getAllInformation,boolean login){
		try {
			response.sendRedirect(getUserSNSUrl(getAllInformation, login, PathUtil.getQueryUrl(request),WechatConstantParamter.wechatQrconnectLoginUrl,"snsapi_login"));
		} catch (IOException e) {
			throw new FrameworkException(e);
		}
	}
	/**
	 * 获取网页扫描二维码登陆的微信用户(若没登陆,则会自动登陆)
	 * @param response
	 * @param request
	 * @param getAllInformation 是否获取用户的昵称等其他信息,若为false则只获取openid用
	 * @return 登陆的微信用户(没登陆,则会返回null,然后重定向到微信登陆页面)
	 */
	public static WechatUser getQrconnectLoginWechatUser(HttpServletResponse response, HttpServletRequest request,boolean getAllInformation){
		Object attribute = request.getSession().getAttribute(wechatUserSessionKey);
		if (attribute == null) {
			qrconnectLogin(response, request, getAllInformation, true);
		}
		return (WechatUser) attribute;
	}
	/**
	 * 拉取用户信息(网页登陆用)
	 * @param access_token
	 * @param openid
	 * @return 微信用户
	 * 
	 * @see #pullUserInformationByOpenid(String, String)
	 */
	public static WechatUser pullUserInformation(String access_token,String openid){
		return pullUserInformation(access_token, openid, null);
	}
	/**
	 * 拉取用户信息(网页登陆用)
	 * @param access_token
	 * @param openid
	 * @param lang 语言 一般传'zh_CN',传null则默认为"zh_CN"
	 * @return 微信用户
	 * 
	 * @see #pullUserInformationByOpenid(String, String)
	 */
	public static WechatUser pullUserInformation(String access_token,String openid,String lang){
		if (lang == null) {
			lang = "zh_CN";
		}
		String url = fitParam2Url(WechatConstantParamter.pullUserInformationUrl)
			.replace("#PAGE_ACCESS_TOKEN#", access_token)
			.replace("#OPENID#", openid).replace("#LANG#", lang);
		return pullWechatUser(url);
	}
	
	/**
	 * 拉取微信用户信息
	 * @param openid
	 * @return 
	 * @see WechatInterfaceInvokeUtil#pullUserInformation(String, String, String)
	 */
	public static WechatUser pullUserInformationByOpenid(String openid){
		return pullUserInformationByOpenid(openid, null);
	}
	
	/**
	 * 
	 * 同步当前公众号的粉丝即wechatuser实体类 (openids较多的话,耗时较长,请另外启动线程执行)
	 * 
	 * @param openids 可以从<code> {@link #getOpenids(String)} </code>的返回值里面拿
	 * @param forceSynchronous 是否强制同步
	 * (若为false,则若数据库里面已经有了对应的openid记录,就不会再调接口拉取该用户的信息)
	 */
	public static void synchronizationUsers(String[] openids,boolean forceSynchronous){
		Set<String> openidsSaved = queryOpenidSaved(openids);
		if (!forceSynchronous) {
			openids = trimOpenids(openids, openidsSaved);
		}
		String[][] openidGroups = spliteOpenids(openids);
		List<String> unbscribe = new ArrayList<>();
		for (String[] temp:openidGroups) {
			WechatUsers pullUserInformationByOpenids = pullUserInformationByOpenids(temp);
			for(WechatUser user:pullUserInformationByOpenids.getUser_info_list()){
				if (user.getSubscribe() == 0) {
					unbscribe.add(user.getOpenid());
				}else {
					user.setWechatAccountAppid(MutiAccountSupportUtil.getWechatAccount().getAppID());
					if (forceSynchronous) {
						if (openidsSaved.contains(user.getOpenid())) {
							user.update();
						}else {
							user.save();
						}
					}else {
						user.save();
					}
				}
			}
		}
		if (!unbscribe.isEmpty()) {
			QueryBuilder queryBuilder = new QueryBuilder();
			queryBuilder.addIn("openid", openids);
			Map<String, Object> updated = new HashMap<>();
			updated.put("subscribe", 0);
			DaoUtil.updateByQuery(WechatUser.class, queryBuilder.getWhere(), updated);
		}
	}

	private static String[] trimOpenids(String[] openids, Set<String> openidsSaved) {
		Set<String> set = new HashSet<>(Arrays.asList(openids));
		for (String temp:openidsSaved) {
			set.remove(temp);
		}
		openids = set.toArray(new String[set.size()]);
		return openids;
	}

	private static Set<String> queryOpenidSaved(String[] openids) {
		Set<String> openidsSaved = new HashSet<>();
		QueryBuilder queryBuilder = new QueryBuilder();
		queryBuilder.addIn("openid", openids);
		List<String> listAllCustomField = DaoUtil.listAllCustomField(queryBuilder.getWhere(), WechatUser.class, "openid");
		openidsSaved.addAll(listAllCustomField);
		return openidsSaved;
	}


	/**
	 * <code> {@link #pullUserInformationByOpenids(String[])}</code> 一次只能拉100个用户信息,所以要分割openid
	 * @param openids
	 * @return
	 */
	private static String[][] spliteOpenids(String[] openids) {
		int maxlength = 100;
		String[][] openidGroups = new String[openids.length/maxlength+1][];
		for (int i = 0; i < openidGroups.length; i++) {
			int length = maxlength;
			if (i == openidGroups.length-1) {
				length = openids.length%100;
			}
			openidGroups[i] = new String[length];
		}
		for (int i = 0; i < openids.length; i++) {
			openidGroups[i/100][i%100] = openids[i];
		}
		return openidGroups;
	}
	
	/**
	 * 同步当前公众号的粉丝
	 * @param forceSynchronous 是否强制同步
	 * (若为false,则若数据库里面已经有了对应的openid记录,就不会再调接口拉取该用户的信息)
	 * 
	 * @param commitTransactionOnSynchronousing 是否同步中途提交事务(若粉丝太多,请传true,否则造成一个事务插入太多wechatuser记录,导致事务提交失败)
	 */
	public static void synchronizationUsers(boolean forceSynchronous,boolean commitTransactionOnSynchronousing){
		Openids openids = getOpenids(null);
		while (!StringUtil.isStringEmpty(openids.getNext_openid())) {
			synchronizationUsers(openids.getData().getOpenid(),forceSynchronous);
			if (commitTransactionOnSynchronousing) {
				DaoUtil.managTransaction();
			}
			openids = getOpenids(openids.getNext_openid());
		}
	}
	
	
	/**
	 * 拉取微信用户信息
	 * @param openid
	 * @param lang 语言 一般传'zh_CN',传null则默认为"zh_CN"
	 * @return 
	 * @see WechatInterfaceInvokeUtil#pullUserInformation(String, String, String)
	 */
	public static WechatUser pullUserInformationByOpenid(String openid,String lang){
		if (lang == null) {
			lang = "zh_CN";
		}
		String url = fitParam2Url("https://api.weixin.qq.com/cgi-bin/user/info?access_token=#ACCESS_TOKEN#&openid=#OPENID#&lang=#LANG#")
				.replace("#OPENID#", openid).replace("#LANG#", lang);
		return pullWechatUser(url);
	}
	
	/**
	 * 批量拉取微信用户信息
	 * @param openids 要拉取的用户openid,一次最多拉100个
	 * @return 
	 * @see WechatInterfaceInvokeUtil#pullUserInformation(String, String, String)
	 */
	public static WechatUsers pullUserInformationByOpenids(String[] openids){
		return pullUserInformationByOpenids(openids, null);
	}
	
	/**
	 * 批量拉取微信用户信息
	 * @param openids 要拉取的用户openid,一次最多拉100个
	 * @param lang 语言 一般传'zh_CN',传null则默认为"zh_CN"
	 * @return 
	 * @see WechatInterfaceInvokeUtil#pullUserInformation(String, String, String)
	 */
	public static WechatUsers pullUserInformationByOpenids(String[] openids,String lang){
		if (openids.length > 100) {
			throw new IllegalArgumentException("一次最多拉100个微信用户信息!");
		}
		UserQueryDomain[] userQueryDomains = new UserQueryDomain[openids.length];
		for (int i = 0; i < userQueryDomains.length; i++) {
			userQueryDomains[i] = new UserQueryDomain(openids[i], lang);
		}
		Map<String, Object> map = new HashMap<>();
		map.put("user_list", userQueryDomains);
		return invokeWechatInterface(map, WechatUsers.class, "https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=#ACCESS_TOKEN#");
	}
	
	private static WechatUser pullWechatUser(String url) {
		WechatUser wechatuser = invokeWechatInterface(null, WechatUser.class, url);
		wechatuser.setWechatAccountAppid(MutiAccountSupportUtil.getWechatAccount().getAppID());
		return wechatuser;
	}
	
	
	/**
	 * 根据code拉取用户信息
	 */
	public static WechatUser pullUserInformation(String code){
		PageAccessToken pageAccessToken = getPageAccessToken(code);
		return pullUserInformation(pageAccessToken.getAccess_token(), pageAccessToken.getOpenid(), "zh_CN");
	}
	
	/**
	 * 用code兑换PageAccessToken
	 * @param code
	 * @return
	 */
	public static PageAccessToken getPageAccessToken(String code){
		String url = fitParam2Url(WechatConstantParamter.getAuthorAssessTokenUrl).replace("#CODE#", code);
		return invokeWechatInterface(null, PageAccessToken.class, url);
	}
	
	/**
	 * 获取用于拉取用户信息重定向的url
	 * @param getAllInformation 是否获取用户除openID外的其他信息
	 * @param goUrl 获取用户信息成功后跳转的url
	 * @return
	 */
	public static String getUserSNSUrl(boolean getAllInformation,boolean login,String goUrl,String oauthUrl,String scope){
		String fitParam2Url = fitParam2Url(oauthUrl);
		String state = StringUtil.getSecureRandomString();
		fitParam2Url = fitParam2Url.replace("#SCOPE#", scope)
				.replace("#STATE#", state)
				.replace("#REDIRECT_URI#", StringUtil.encodeUrl(PathUtil.getBrowserPath("/system/getWechatUserInformation.act")));
		HashMap<String, Object> map = new HashMap<String, Object>();
		map.put("goUrl", goUrl);
		map.put("appID", MutiAccountSupportUtil.getWechatAccount().getAppID());
		map.put("login", login);
		map.put("getAllInformation", getAllInformation);
		DataCacheUtil.setData(state, "wechatSNSSate", map);
		return fitParam2Url;
	}
	
	public static void getAddressShareToken(HttpServletResponse response, HttpServletRequest request){
		try {
			response.sendRedirect(getAddressShareTokenUrl(PathUtil.getQueryUrl(request)));
		} catch (IOException e) {
			e.printStackTrace();
			throw new FrameworkException(e);
		}
	}
	
	public static String getAddressShareTokenUrl(String redirectUri){
		String fitParam2Url = fitParam2Url(WechatConstantParamter.getAuthorCodeUrl);
		String scope = "snsapi_base";
		String state = System.currentTimeMillis()+""+new Random().nextInt();
		fitParam2Url = fitParam2Url.replace("#SCOPE#", scope)
				.replace("#STATE#", state)
				.replace("#REDIRECT_URI#", StringUtil.encodeUrl(redirectUri));
		//Map<String, Object> map = new HashMap<String, Object>();
		//map.put("appID", MutiAccountSupportUtil.getWechatConfig().getAppID());
		DataCacheUtil.setData(state, "addressShareAppId", MutiAccountSupportUtil.getWechatAccount().getAppID());
		return fitParam2Url;
	}
	
	/**
	 * 下载多媒体素材
	 * @param mediaID 媒体素材ID
	 * @return 浏览器能访问到的相对路径
	 */
	public static String downLoadMidea(String mediaID){
		CloseableHttpResponse response = null;
		try {
			HttpRequestBase buildRequest = HttpUtil.buildRequest(HttpUtil.http_get, fitParam2Url(WechatConstantParamter.getMediaUrl).replace("#MEDIA_ID#", mediaID), null, null);
			response = HttpClients.createDefault().execute(buildRequest);
			
			String fileName = response.getFirstHeader("Content-disposition").getValue();
			fileName = fileName.substring("attachment; filename=\"".length(), fileName.length()-1);
			
			HttpEntity entity = response.getEntity();
			byte[] bit = EntityUtils.toByteArray(entity);
			File file = new File(GlobalParam.FOLDER.rootPath+"wechatmedia\\"+fileName);
			file.mkdirs();
			file.delete();
			FileOutputStream fileOutputStream = new FileOutputStream(file);
			fileOutputStream.write(bit);
			fileOutputStream.flush();
			fileOutputStream.close();
			return "/wechatmedia/"+fileName;
		} catch (Exception e) {
			BaseUtil.getLogger().error("下载微信媒体素材出错",e);
		}finally{
			if (response != null) {
				try {
					response.close();
				} catch (IOException e) {
					
				}
			}
		}
		return null;
	}
	
	
	/****************************\支付相关******************************/
	
	/**
	 * 订单查询接口
	 * @return
	 */
	public static OrderQueryResult queryOrder(OrderQuerySend orderQuerySend){
		WechatAccount wechatConfig = MutiAccountSupportUtil.getWechatAccount();
		orderQuerySend.setMch_id(wechatConfig.getMerchantId());
		orderQuerySend.setAppid(wechatConfig.getAppID());
		orderQuerySend.setNonce_str(Md5Util.Md5Digest(System.currentTimeMillis()+""+new Random().nextInt()));
		orderQuerySend.setSign(null);
		orderQuerySend.setSign(SignUtil.signObj(orderQuerySend, "MD5", true).toUpperCase());
		String orderQuerySendToXml = MessageUtil.toXml(orderQuerySend);
		log.debug("查询订单接口，发送数据----->{}",new Object[]{orderQuerySendToXml});
		try {
			return MessageUtil.fromXml(HttpUtil.getRequestString(HttpUtil.http_post,WechatConstantParamter.queryOrderUrl, null, null, orderQuerySendToXml),OrderQueryResult.class);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	

	/**
	 * 获取统一订单
	 * @param tradeType 交易类型,取值如下：JSAPI，NATIVE，APP，WAP,详细说明见
	 * @param body 订单信息
	 * @param totalFee 金额(单位:分)
	 * @param orderNumber 订单号,不传的话则自动生成订单号
	 * @return
	 */
	public static UnifiedOrderSend getUnifiedOrderSend(String tradeType,String body,int totalFee,String orderNumber) {
		UnifiedOrderSend orderSend = new UnifiedOrderSend();
		orderSend.setBody(body);
		if (orderNumber == null) {
			orderNumber = FlowUtil.getFlowString(MutiAccountSupportUtil.getWechatAccount().getAppID()+"tempOrderNumber", 6, true);
		}
		orderSend.setOut_trade_no(orderNumber);
		orderSend.setTotal_fee(totalFee);
		orderSend.setTrade_type(tradeType);
		try{
			orderSend.setSpbill_create_ip(ServletActionContext.getRequest().getRemoteAddr());
			orderSend.setOpenid(((WechatUser)ServletActionContext.getRequest().getSession().getAttribute("wechatUser")).getOpenid());
		}catch (Exception e) {
		}
		return orderSend;
	}
	
	
	/**
	 * 统一下单接口
	 * @return
	 * 
	 * @see #getUnifiedOrderSend
	 */
	public static UnifiedOrderResult unifiedOrder(UnifiedOrderSend unifiedOrderSend){
		WechatAccount wechatConfig = MutiAccountSupportUtil.getWechatAccount();
		unifiedOrderSend.setMch_id(wechatConfig.getMerchantId());
		unifiedOrderSend.setSign(null);
		unifiedOrderSend.setAppid(wechatConfig.getAppID());
		if (unifiedOrderSend.getNotify_url() == null) {
			unifiedOrderSend.setNotify_url(PathUtil.getBrowserPath("/wechatPayCallBackServlet.jsp"));
		}
		SignUtil.signUnifiedOrderSend(unifiedOrderSend);
		String redPacketSendMessageToXml = MessageUtil.unifiedOrderSendToXml(unifiedOrderSend);
		try {
			UnifiedOrderResult xmlToUnifiedOrderResult = MessageUtil.XmlToUnifiedOrderResult(HttpUtil.getRequestString(HttpUtil.http_post,WechatConstantParamter.unifiedOrderUrl, null, null, redPacketSendMessageToXml));
			if (xmlToUnifiedOrderResult.isSuccess()) {
				Property property = new Property();
				property.setPropertyType("wechatOrder");
				property.setKee(unifiedOrderSend.getOut_trade_no()+","+wechatConfig.getAppID());
				property.setValue(wechatConfig.getAppID());
				property.save();
			}
			return xmlToUnifiedOrderResult;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 
	 * @param body
	 * @param money
	 * @param clientIP
	 * @param TradeType
	 * @param openId
	 * 
	 * @see #getUnifiedOrderSend(String, int, String, String, String)
	 * @return
	 */
	public static UnifiedOrderResult unifiedOrder(String body, int money,String clientIP,String TradeType,String openId){
		UnifiedOrderSend orderSend = getUnifiedOrderSend(body,money,clientIP,TradeType,openId);
		return WechatInterfaceInvokeUtil.unifiedOrder(orderSend);
	}
	
	/**
	 * 对下单接口返回来的prepay_id签名,用于客户端或网页端支付用
	 * @param unifiedOrder
	 * @return
	 */
	public static Map<String, Object> signPayJsSdk(UnifiedOrderResult unifiedOrder) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("appId", MutiAccountSupportUtil.getWechatAccount().getAppID());
		Long value = (Long)(System.currentTimeMillis()/1000);
		map.put("timeStamp", value);
		map.put("nonceStr", Md5Util.Md5Digest("sted"+value));
		map.put("package", "prepay_id="+unifiedOrder.getPrepay_id());
		map.put("signType", "MD5");
		map.put("paySign", SignUtil.signMap(map, "MD5",true).toUpperCase());
		return map;
	}
	/**
	 * 返回wx.config()所需要的参数如果是在jsp页面调用,建议直接用{@link steed.tag.wechat.WechatJsApiSupport}
	 * @param pageUrl 要调用js api的页面路径,包括?id=xx这样的参数,注意是页面url,不是ajax url!(传null则根据当前request获取)
	 * @param jsApiList 要使用js api名称(传null表示使用所有api....目前在下没空把所有api列出来,暂时不要传null)
	 * @see PathUtil#getQueryUrl
	 * @return
	 */
	public static Map<String, Object> jsApi(String pageUrl,String... jsApiList ) {
		Map<String, Object> map2 = new HashMap<String, Object>();
		Long value = (Long)(System.currentTimeMillis()/1000);
		String noncestr = Md5Util.Md5Digest(value+"steed");
		map2.put("noncestr", noncestr);
		map2.put("timestamp", value);
		map2.put("jsapi_ticket", JsapiTicketUtil.getJsapiTicket().getTicket());
		if (pageUrl == null) {
			map2.put("url", PathUtil.getQueryUrl(ContextUtil.getRequest()).replace("/WEB-INF/jsp", ""));
		}else {
			map2.put("url", pageUrl);
		}
		String signature = SignUtil.signMap(map2, "SHA1");
		map2.put("signature", signature);
		//干你娘的狗日微信,字段大小写能统一一下?微信支付也是这屌样
		map2.put("nonceStr", noncestr);
		map2.remove("noncestr");
		//TODO if (jsApiList == null){添加所有jsapi}
		map2.put("jsApiList", jsApiList);
		map2.put("appId", MutiAccountSupportUtil.getWechatAccount().getAppID());
		
		return map2;
	}
	
	/**
	 * 
	 * @param money
	 * @param clientIP
	 * @param TradeType 交易类型,取值如下：JSAPI，NATIVE，APP，WAP,详细说明见
	 * @param openId
	 * @param body 订单信息
	 * @return
	 */
	public static UnifiedOrderSend getUnifiedOrderSend(String body,int money,String clientIP,String TradeType,String openId) {
		UnifiedOrderSend orderSend = new UnifiedOrderSend();
		orderSend.setAttach("type_pay");
		orderSend.setBody(body);
		String orderKey = "steedWechatOrderNumber";
		WechatAccount wechatAccount = MutiAccountSupportUtil.getWechatAccount();
		if (wechatAccount != null) {
			orderKey = wechatAccount.getAppID() + "WechatOrderNumber";
		}
		orderSend.setOut_trade_no(FlowUtil.getFlowString(orderKey, 6, true));
		orderSend.setTotal_fee(money);
		orderSend.setNotify_url(PathUtil.getBrowserPath("/wechat/scanPayCallBackServlet.jsp"));
		orderSend.setSpbill_create_ip(clientIP);
		orderSend.setTrade_type(TradeType);
		orderSend.setOpenid(openId);
		return orderSend;
	}
	/**
	 * 红包发放接口
	 * @return
	 */
	public static RedPacketResult sendRedPacket(RedPacketSend redPacketSend){
		WechatAccount wechatConfig = MutiAccountSupportUtil.getWechatAccount();
		redPacketSend.setMch_id(wechatConfig.getMerchantId());
		if (StringUtil.isStringEmpty(redPacketSend.getSign())) {
			SignUtil.signRedPacketSend(redPacketSend);
		}
		String redPacketSendMessageToXml = MessageUtil.redPacketSendMessageToXml(redPacketSend);
		try {
			return MessageUtil.XmlToRedPacketSendMessage(HttpUtil.getRequestString(fitParam2Url(WechatConstantParamter.sendRedPacketUrl), null, null, redPacketSendMessageToXml, wechatConfig.getMerchantCertPath(), wechatConfig.getMerchantId()));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 退款
	 * @return
	 */
	public static RefundResult refund(RefundSend refund){
		WechatAccount wechatConfig = MutiAccountSupportUtil.getWechatAccount();
		SignUtil.signRefound(refund);
		try {
			String xml = HttpUtil.getRequestString("https://api.mch.weixin.qq.com/secapi/pay/refund", null, null, MessageUtil.toXml(refund), wechatConfig.getFullMerchantCertPath(), wechatConfig.getMerchantId());
			return MessageUtil.fromXml(xml, RefundResult.class);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/****************************#支付相关******************************/
	/**
	 * 上传临时素材接口
	 * @param filePath
	 * @return
	 */
	public static AttachUploadResult uploadAttach(String filePath) {
		 CloseableHttpClient httpclient = HttpClients.createDefault();
	        try { 
	        	String url = WechatInterfaceInvokeUtil.fitParam2Url(WechatConstantParamter.uploadAttachUrl);
	        	String attachType;
//	        	if (isthumb) {
//					attachType = "thumb";
//				}else {
					attachType = getAttachType(FileUtil.getFileSuffix(filePath));
//				}
				url = url.replace("#TYPE#", attachType);
	        	
	            HttpPost httppost = new HttpPost(url); 
	            FileBody img = new FileBody(new File(filePath)); 
	            StringBody filename = new StringBody(img.getFilename(), ContentType.TEXT_PLAIN); 
	            HttpEntity reqEntity = MultipartEntityBuilder.create() 
	                    .addPart("img", img) 
	                    .addPart("filename", filename) 
	                    .build();
	            httppost.setEntity(reqEntity); 
	            CloseableHttpResponse response = httpclient.execute(httppost); 
	            try { 
	                HttpEntity resEntity = response.getEntity(); 
                    AttachUploadResult attachUploadResult = new Gson().fromJson(EntityUtils.toString(resEntity), AttachUploadResult.class);
	                EntityUtils.consume(resEntity); 
	                return attachUploadResult;
	            } finally {
	                response.close(); 
	            } 
	        }catch (IOException e) {
				e.printStackTrace();
				throw new WechatIoException(e);
			}  finally { 
	            try {
					httpclient.close();
				} catch (IOException e) {
					e.printStackTrace();
				} 
	        } 
	}
	
	/**
	 * 调用微信接口
	 * @param objSend 要发送的json实体类
	 * @param clazz 微信响应实体类
	 * @param url 接口地址
	 * @return 接口调用结果
	 */
	public static <T extends WechatResult> T invokeWechatInterface(Object objSend,Class<T> clazz,String url){
		String json = null;
		if (objSend != null) {
			json = BaseUtil.getJson(objSend);
		}
		String requestString = HttpUtil.getRequestString(HttpUtil.http_post, fitParam2Url(url), null, null, json);
		BaseUtil.getLogger().debug("调用微信接口,url:{},发送json数据:{},返回结果:{}",url,json,requestString);
		return BaseUtil.parseJson(requestString, clazz);
	}
	
	/*******************************#接口***********************/
	
	
	
	/***************************\私有方法********************/
	
	
	private static String getAttachType(String suffix){
		String lowerCase = suffix.toLowerCase();
		//我***，为兼容jdk1.6,不能用switch
		if (".jpg".equals(lowerCase) 
				|| ".jpeg".equals(lowerCase)
				|| ".png".equals(lowerCase)
				|| ".gif".equals(lowerCase)) {
			return "image";
		}else if(".mp4".equals(lowerCase)){
			return "video";
		}else if(".mp3".equals(lowerCase)
				|| ".wma".equals(lowerCase)
				|| ".wav".equals(lowerCase)
				|| ".arm".equals(lowerCase)){
			return "voice";
		}else {
			throw new AttachTypeNotSupportedException("暂时不支持上传"+suffix+"格式的素材！！");
		}
	}
	/***************************#私有方法********************/
	
}
